package com.inepex.inechart.client.displayengine;

import java.util.ArrayList;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.Widget;

/**
 * 
 * This widget displays its child widgets based on the actual view port position and the position of its children which can be:
 * - explicit coordinates -> use {@link #addAbsolutePositionedWidget(AbsolutePositionedWidget)}
 * - position via its {@link TileTable} -> use relevant addXY or setXY methods
 * 
 * It works like a {@link Panel} - automatically attaches and detaches (only if {@link #setDetachHiddenTiles(boolean)} is set true) its children,
 * It means that the {@link RectangleTile}s and {@link AbsolutePositionedWidget}s must not be attached to DOM while using this widget.
 * 
 * @author Miklos Suveges / Inepex Ltd.
 *
 */
public class ViewportWidget extends Composite {

	protected int width;
	protected int height;
	protected int tileWidth;
	protected int tileHeight;

	protected int top;
	protected int left;
	protected int viewportZ;

	protected TileOwner tileOwner;
	protected FlowPanel viewport;
	protected TileTable tileTable;

	protected ArrayList<RectangleTile> attachedTiles;
	protected ArrayList<RectangleTile> visibleTiles;
	protected boolean detachHiddenTiles;

	protected ArrayList<AbsolutePositionedWidget> absolutePositionedWidgets;

	public ViewportWidget(int width, int height) {
		this(width, height, width, height);
	}

	public ViewportWidget(int width, int height, int tileWidth, int tileHeight) {
		super();
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		tileTable = new TileTable();
		attachedTiles = new ArrayList<RectangleTile>();
		visibleTiles = new ArrayList<RectangleTile>();
		absolutePositionedWidgets = new ArrayList<AbsolutePositionedWidget>();

		viewport = new FlowPanel();
		setSize(width, height);
		DOM.setStyleAttribute(viewport.getElement(), "position", "relative");
		DOM.setStyleAttribute(viewport.getElement(), "overflow", "hidden");
		initWidget(viewport);
	}

	
	public void clear(){
		viewport.clear();
		attachedTiles.clear();
		visibleTiles.clear();
		absolutePositionedWidgets.clear();
	}
	
	/**
	 * Instantly refreshes the view port's position attaching previously hidden tiles (if needed).
	 */
	public void update(){
		int row = (int) Math.floor(top / (double)tileHeight);
		int col = (int) Math.floor(left / (double)tileWidth);
		int coveredBot = row * tileHeight;
		int coveredRight = col * tileWidth;
		ArrayList<RectangleTile> newVisibleTiles = new ArrayList<RectangleTile>();
		int colD = 0, rowD = 0;
		do{ //find visible tiles (tiles in viewport)
			do{
				RectangleTile actTile = tileTable.getTile(row + rowD, col + colD);
				if(actTile != null){
					newVisibleTiles.add(actTile);
				}				
				coveredRight += tileWidth;
				colD++;
			}
			while(coveredRight < left + width);
			coveredBot += tileHeight;
			coveredRight = col * tileWidth;
			rowD++;
			colD = 0;
		}
		while(coveredBot < top + height);

		ArrayList<RectangleTile> justHidden = new ArrayList<RectangleTile>(visibleTiles);
		ArrayList<RectangleTile> justShown = new ArrayList<RectangleTile>(newVisibleTiles);
		justHidden.removeAll(newVisibleTiles);
		justShown.removeAll(visibleTiles);

		/* event firing and attach to document */
		for (RectangleTile tile : justShown) {
			if(tileOwner != null){
				tileOwner.beforeShow(tile);
			}
			if(!attachedTiles.contains(tile)){ //must attach first
				viewport.add(tile.asWidget());
				attachedTiles.add(tile);
			}
		}
		if(tileOwner != null){
			for (RectangleTile tile : justHidden) {
				tileOwner.beforeShow(tile);
			}
		}

		/* detach from document */
		if(detachHiddenTiles){
			ArrayList<RectangleTile> toRemove = new ArrayList<RectangleTile>();
			for (RectangleTile tile : attachedTiles) {
				if(!newVisibleTiles.contains(tile)){
					viewport.remove(tile.asWidget());
					toRemove.add(tile);
				}
			}
			attachedTiles.removeAll(toRemove);
		}

		/* set new positions */
		for (RectangleTile tile : attachedTiles) {
			setTilePosition(tile);
		}
	}

	@Override
	protected void onLoad() {
		try{
			viewportZ = Integer.parseInt(getElement().getStyle().getZIndex());
		}
		catch (Exception e) {
			viewportZ = 0;
		}
	}

	protected void setTilePosition(RectangleTile tile){
		int viewportX = tile.column * tileWidth - left;
		int viewportY = tile.row * tileHeight - top;
		setWidgetPosition(tile.asWidget(), viewportX, viewportY);
	}

	protected void setWidgetPosition(Widget widget, int left, int top){
		DOM.setStyleAttribute(widget.getElement(), "position", "absolute");
		DOM.setStyleAttribute(widget.getElement(), "left", left + "px");
		DOM.setStyleAttribute(widget.getElement(), "top", top + "px");

		//		DOM.setStyleAttribute(tile.widget.getElement(), "zIndex", "" + (viewportZ + 21));	
	}

	/**
	 * Sets the position of the view port.
	 * Note that this method is usually followed by {@link #update()} call 
	 * @param top 
	 * @param left
	 */
	public void setPosition(int left, int top){
		this.top = top;
		this.left = left;
	}

	/**
	 * Moves the view port.
	 * Note that this method is usually followed by {@link #update()} call 
	 * @param dy
	 * @param dx
	 */
	public void moveViewport(int dx, int dy){
		this.left += dx;
		this.top += dy;
	}

	/**
	 * Sets the size of the view port (and also this widget's)
	 * @param width
	 * @param height
	 */
	public void setSize(int width, int height) {
		this.width = width;
		this.height = height;
		viewport.setPixelSize(width, height);
	}
	
	/**
	 * Sets the size of all child tiles
	 * @param tileWidth
	 * @param tileHeight
	 */
	public void setTileSize(int tileWidth, int tileHeight) {
		this.tileWidth = tileWidth;
		this.tileHeight = tileHeight;
		tileTable.setTileSizes(tileWidth, tileHeight);
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public TileTable getTileTable() {
		return tileTable;
	}
	
	public CartesianRectangleTile createCartesianRectangleTile(int row, int col){
		CartesianRectangleTile tile = new CartesianRectangleTile(row, col, tileWidth, tileHeight);
		tileTable.setTile(tile, row, col);
		return tile;
	}

	public void addAbsolutePositionedWidget(AbsolutePositionedWidget widget){
		viewport.add(widget.getWidget());
		absolutePositionedWidgets.add(widget);
	}

	public void updateAbsolutePositionedWidget(AbsolutePositionedWidget widget){
		setWidgetPosition(widget.getWidget(), widget.getLeft() - left, widget.getTop() - top);
	}

	public void updateAllAbsolutePositionWidgets(){
		for (AbsolutePositionedWidget apw : absolutePositionedWidgets) {
			updateAbsolutePositionedWidget(apw);
		}
	}

	public TileOwner getTileOwner() {
		return tileOwner;
	}

	public void setTileOwner(TileOwner tileOwner) {
		this.tileOwner = tileOwner;
	}

	public boolean isDetachHiddenTiles() {
		return detachHiddenTiles;
	}

	public void setDetachHiddenTiles(boolean detachHiddenTiles) {
		this.detachHiddenTiles = detachHiddenTiles;
	}

}
